#!/usr/bin/env python
# encoding: utf-8

"""
Command line script to access the Bot application.
"""

# Import django settings (yeah, this is a bit difficult... :/)

from django.core.management import setup_environ
import sys
from os.path import dirname

try:
  sys.path.append(dirname(dirname(__file__)))

  import settings # Assumed to be in the same directory.
except ImportError:
  sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r.\n" % __file__)
  sys.exit(1)

  # Import necessary models

# If we're the base script, we need to include the Django configuration
if __name__ == "__main__":
  setup_environ(settings)

from testbot.models import Position
from track.models   import Vehicle

# Additional module imports

from cookielib import CookieJar
from optparse  import OptionParser
from optparse  import OptionGroup
from random    import randint
from time      import sleep
from urllib2   import build_opener
from urllib2   import HTTPCookieProcessor
from urllib    import urlencode

def _doMove(opener, server, action, vehicle, x, y):
  """Call the url to 'action' 'vehicle' on 'server' by/to (x, y)"""
  
  url = 'http://%(server)s/track/%(action)s/%(x)s/%(y)s/%(vehicle)s/'%{
    'server':  server,
    'action':  action,
    'x':       x,
    'y':       y,
    'vehicle': vehicle}
  
  try:
    opener.open(url).read()
  except Exception, e:
    print e
    exit(1)

def _doSleep(sleepTime, progressPercentage, remainingTime):
  """Sleep for a bit, with feedback"""
  print '(%d%%) Sleeping for %ds, %ds remaining'%(
    int(progressPercentage * 100.),
    sleepTime,
    int(remainingTime))
  sys.stdout.flush()
  
  sleep(sleepTime)

def main(server='127.0.0.1:8000', step=1.0, iterations=1, random=False, entropy=1.0, x_drift=0.0, y_drift=0.0):
  """The main testbot method"""
  # ##############
  # Initialization
  # ##############
  
  # Check that parameters are valid
  if iterations < 1 or int(iterations) != iterations:
    raise ValueError, 'iterations parameter should be an integer >=1'
  
  print 'Start TestBot run against', server, 'with time step', step,
  print 'and iterate', iterations, 'times using', 'random' if random else 'predefined', 'walk'
  
  if not random:
    # Load positions run against, and meta data about these positions
    positions = Position.objects.all()
    posCount  = len(positions)
    minTime   = positions[0].time if posCount > 0 else 0
    maxTime   = positions[posCount - 1].time if posCount > 0 else 0
    
    print 'We will need to change positions %d times and it will take %ds.'%(posCount, (maxTime - minTime) * step)
    
    if posCount == 0:
      print 'No positions to set, exiting.'
      sys.exit(0)
  else:
    vehicleSlugs = [vehicle.slug for vehicle in Vehicle.objects.all()]
    print "We will be walking and waiting randomly, and don't really know how long it'll take"
  
  # ########################
  # Authentication to server
  # ########################
  print 'Authenticating to server...'
  
  cj     = CookieJar()
  opener = build_opener(HTTPCookieProcessor(cj))
  r = opener.open(
    "http://%s/accounts/login/"%server,
    urlencode([('username', 'testbot'), ('password', settings.TESTBOT_PASSWORD)]))
  
  r.read()
  
  currentIteration = 0
  
  # ################
  # Animate vehicles
  # ################
  while currentIteration < iterations:
    currentIteration += 1
    
    if iterations > 1:
      print 'Iteration', currentIteration, '/', iterations
    
    if random:
      for vehicle in vehicleSlugs:
        _doMove(
          opener, server,
          'translate', vehicle,
          randint(-5, 5) / 10. * entropy + x_drift,
          randint(-5, 5) / 10. * entropy + y_drift)
      
      _doSleep(
        sleepTime=step,
        progressPercentage=currentIteration / float(iterations),
        remainingTime=(iterations - currentIteration) * step)
    else:
      currentTime = positions[0].time
      progress    = 0.
      
      # Loop on all poitions and set each vehicles position, sleeping when necessary
      for position in positions:
        if currentTime < position.time:
          _doSleep(
            sleepTime=(position.time - currentTime) * step,
            progressPercentage=progress / posCount,
            remainingTime=(maxTime - minTime) * step * (1. - progress / posCount))
          
          currentTime = position.time
        
        # Set new vehicle position
        _doMove(opener, server, 'set', position.vehicle.slug, position.x, position.y)
        
        progress += 1.

if __name__ == '__main__':
  # Define existing options for testbot
  parser = OptionParser(prog='bot.py', usage="%prog [--step STEP]")
  
  (server, step, iterations, random, entropy, x_drift, y_drift) = main.func_defaults
  
  parser.set_defaults(
    step=step, iterations=iterations, server=server,
    random=random, entropy=entropy, x_drift=x_drift, y_drift=y_drift)
  
  parser.add_option(
    '-S', '--server',
    help="Address (ip:port) of the server against which to run the tests [default: %default].")
  
  parser.add_option(
    "-s", "--step", type="float",
    help="Multiplier for the bot's time base [default: %default].")
  
  parser.add_option(
    '-n', '--iterations', type='int',
    help='Number of times to loop on the test run [default: %default].')
  
  group = OptionGroup(parser, "Random walk options")
  
  group.add_option(
    "-R", "--random",
    action="store_true",
    help="Use random walk instead of predefined positions [default: %default]. The following options are only used if this flag is set.")
  
  group.add_option(
    "-e", "--entropy", type="float",
    help="Scale of the random walk [default: %default].")
  
  group.add_option(
    "-x", "--x-drift", type="float",
    help="Longitudinal drift when walking randomly [default: %default].")
  
  group.add_option(
    "-y", "--y-drift", type="float",
    help="Latitudinal drift when walking random [default: %default].")
  
  parser.add_option_group(group)
  
  # Parse command line options to script
  
  (options, args) = parser.parse_args()

  parser.destroy()
  
  # Run testbot based on these options
  main(
    server=options.server,
    step=options.step, iterations=options.iterations,
    random=options.random, entropy=options.entropy,
    x_drift=options.x_drift, y_drift=options.y_drift)
